from functools import reduce
import numpy as np
from math import exp


class Node(object):
    __slots__ = ('layer_index', 'node_index', 'downstream', 'upstream', 'output', 'delta')

    def __init__(self, layer_index, node_index):
        '''
        构造节点对象
        :param layer_index: 节点所属的层的编号
        :param node_index: 节点的编号
        '''
        self.layer_index = layer_index
        self.node_index = node_index
        # 直接上下游连接集合，记录的是connection对象
        self.downstream = []
        self.upstream = []
        # ai
        self.output = 0
        # 对于输出层 δi = yi * (1 - yi) * (ti - yi) OR
        # 对于隐藏层 δj = aj * (1 - aj) * Σ δk * ωkj
        self.delta = 0

    def set_output(self, output):
        '''
        设置节点的输出值。如果节点是输出层会用到这个函数
        :param output: 要输出的数据
        :return:
        '''
        self.output = output

    def append_downstream_connection(self, conn):
        '''
        上下游都是记录在Connection 中的
        添加一个到下游节点的连接
        :param conn: 下游的节点的连接
        :return:
        '''
        self.downstream.append(conn)

    def append_upstream_connection(self, conn):
        '''
        上下游都是记录在Connection 中的
        添加一个到下游节点的连接
        TODO :rtype: object
        :param conn: 下游的节点的连接
        :return:
        '''
        self.upstream.append(conn)

    def calc_output(self):
        '''
        根据 y = sigmoid(x) 或其他激活函数activator 计算点的输出
        :return:
        '''
        # 输出取决于上一次的节点
        output = reduce(lambda ret, conn: ret + conn.upstream_node.output * conn.weight, self.upstream, 0)
        self.output = self.sigmoid(output)

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def calc_hidden_layer_delta(self):
        '''

        :return:
        '''
        # Σ wi * δk (Σ k ε Downstream(j) )
        downstream_delta = reduce(
            # reduce(function, lteration [, initial])
            # 如果传入了 initial 值,那么首先传的就不是sequence 的第一个和第二个元素，而是 initial值和 第一个元素。
            # 经过这样的累计计算之后合并序列到一个单一返回值
            lambda ret, conn: ret + conn.downstream_node.delta * conn.weight, self.downstream, 0.0)
        # δj = aj * (1 - aj) * Σ wi * δk
        self.delta = self.output * (1 - self.output) * downstream_delta

    def calc_output_layer_delta(self, label):
        '''
        δj = aj * (1 - aj) * (tj - aj)
        '''
        self.delta = self.output * (1 - self.output) * (label - self.output)

    def __str__(self):
        '''
        节点信息
        :return:
        '''
        node_str = '%u-%u: output: %f delta: %f' % (self.layer_index, self.node_index, self.output, self.delta)
        downstream_str = reduce(lambda ret, conn: ret + '\n\t' + str(conn), self.downstream, '')
        upstream_str = reduce(lambda ret, conn: ret + '\n\t' + str(conn), self.upstream, '')
        return node_str + '\n\tdownstream:' + downstream_str + '\n\tupstream:' + upstream_str
